package com.xiyou.weblog.admin.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xiyou.weblog.admin.event.ReadArticleEvent;
import com.xiyou.weblog.admin.model.vo.article.*;
import com.xiyou.weblog.admin.service.AdminArticleService;
import com.xiyou.weblog.common.domain.dos.*;
import com.xiyou.weblog.common.domain.mapper.*;
import com.xiyou.weblog.common.enums.ResponseCodeEnum;
import com.xiyou.weblog.common.exception.BizException;
import com.xiyou.weblog.common.utils.PageResponse;
import com.xiyou.weblog.common.utils.Response;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author xiyou
 * @description 文章模块
 */
@Service
@Slf4j
public class AdminArticleServiceImpl implements AdminArticleService {

    @Resource
    private ArticleMapper articleMapper;
    @Resource
    private ArticleTagRelMapper articleTagRelMapper;
    @Resource
    private ArticleContentMapper articleContentMapper;
    @Resource
    private ArticleCategoryRelMapper articleCategoryRelMapper;
    @Resource
    private CategoryMapper categoryMapper;
    @Resource
    private  TagMapper tagMapper;
    /**
     * 发布文章
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response publishArticle(PublishArticleReqVO publishArticleReqVO) {
        // VO 转 ArticleDO
        ArticleDO articleDO = ArticleDO.builder()
                .title(publishArticleReqVO.getTitle())
                .cover(publishArticleReqVO.getCover())
                .summary(publishArticleReqVO.getSummary())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();
        articleMapper.insert(articleDO);
        // 插入记录的主键
        Long articleId = articleDO.getId();

        // VO 转 ArticleContentDO
        ArticleContentDO articleContentDO = ArticleContentDO.builder()
                .articleId(articleId)
                .content(publishArticleReqVO.getContent())
                .build();
        // 插入
        articleContentMapper.insert(articleContentDO);

        // 处理文章关联的分类
        Long categoryId = publishArticleReqVO.getCategoryId();

        // 校验提交的分类是否存在
        CategoryDo categoryDo = categoryMapper.selectById(categoryId);
        if (Objects.isNull(categoryDo)) {
            log.warn("==> 分类不存在，categoryId:{}", categoryId);
            throw  new BizException(ResponseCodeEnum.CATEGORY_NOT_EXISTED);
        }

        ArticleCategoryRelDO articleCategoryRelDO = ArticleCategoryRelDO.builder()
                .articleId(articleId)
                .categoryId(categoryId)
                .build();
        articleCategoryRelMapper.insert(articleCategoryRelDO);

        // 保存文章标签关联表
        List<String> tags = publishArticleReqVO.getTags();
        insertTags(articleId,tags);


        return Response.success();

        /*
        我们在发布文章的实现方法上，添加了 @Transactional(rollbackFor = Exception.class) 事务注解，
        表示在该方法执行之前，开启一个事务，保证方法内所有操作是个原子性的，要不全部成功，要不全部失败，
        一旦中间发生异常，则回滚事务。然后在看看方法中的都干了啥？首先将 VO 请求类中的相关字段提取了出来，
        如文章标题、封面等，用来构建 ArticleDO 类，并执行了 insert 新增文章操作，
        然后，拿到了文章 ID, 接着保存文章内容，再接着保存文章分类关联记录，注意，
        保存记录之前，我们首先校验了提交过来的分类是否存在于表中，若不存在，则手动抛出一个业务异常，
        提示该分类不存在；最后是保存了文章标签关联记录。
         */
    }

    /**
     * 批量插入文章的标签(标签不存在则新建标签)
     * @param publishTags 存在的标签传的ID,不存在的标签传的name
     */
    private void insertTags(Long articleId,List<String> publishTags) {
        // 筛选出标签表中不存在的标签
        List<String> notExistTags = null;
        // 筛选出标签表已存在的标签
        List<String> existedTags = null;

        // 查询出表中所有的标签
        List<TagDo> tagDos = tagMapper.selectList(null);

        // 如果表中没有任何标签
        if (CollectionUtils.isEmpty(tagDos)) {
            notExistTags = publishTags;
        }else {
            List<String> tagIds = tagDos.stream().map(tagDo -> String.valueOf(tagDo.getId())).toList();
            // 表中已添加相关标签，则需要筛选
            // 通过标签 ID 来筛选，包含对应 ID 则表示提交的标签是表中存在的
            existedTags = publishTags.stream().filter(tagIds::contains).toList();
            // 否则则是不存在的
            notExistTags = publishTags.stream().filter(publishTag -> !tagIds.contains(publishTag)).toList();
            // 补充逻辑：
            // 还有一种可能：按字符串名称提交上来的标签，也有可能是表中已存在的，比如表中已经有了 Java 标签，用户提交了个 java 小写的标签，需要内部装换为 Java 标签
            Map<String,Long> tagNameIdMap = tagDos.stream().collect(Collectors.toMap(tagDo -> tagDo.getName().toLowerCase(),TagDo::getId));
            // 使用迭代器进行安全的删除操作
            Iterator<String> iterator = notExistTags.iterator();
            while (iterator.hasNext()) {
                String notExistTag = iterator.next();
                // 转小写, 若 Map 中相同的 key，则表示该新标签是重复标签
                if (tagNameIdMap.containsKey(notExistTag.toLowerCase())) {
                    // 从不存在的标签集合中清除
                    iterator.remove();
                    // 并将对应的 ID 添加到已存在的标签集合
                    existedTags.add(String.valueOf(tagNameIdMap.get(notExistTag.toLowerCase())));
                }
            }
        }
        // 将提交的上来的，已存在于表中的标签，文章-标签关联关系入库
        if (!CollectionUtils.isEmpty(existedTags)) {
            List<ArticleTagRelDO> articleTagRelDOS = Lists.newArrayList();
            existedTags.forEach(tagId -> {
                ArticleTagRelDO articleTagRelDO = ArticleTagRelDO.builder()
                        .articleId(articleId)
                        .tagId(Long.valueOf(tagId))
                        .build();
                articleTagRelDOS.add(articleTagRelDO);
            });
            // 批量插入
            articleTagRelMapper.insertBatchSomeColumn(articleTagRelDOS);
        }
        // 将提交的上来的，不存在于表中的标签，入库保存
        if (!CollectionUtils.isEmpty(notExistTags)) {
            // 需要先将标签入库，拿到对应标签 ID 后，再把文章-标签关联关系入库
            List<ArticleTagRelDO> articleTagRelDOS = Lists.newArrayList();
            notExistTags.forEach(tagName -> {
                TagDo tagDo = TagDo.builder()
                        .name(tagName)
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .build();

                tagMapper.insert(tagDo);

                // 拿到保存的标点id
                Long tagId = tagDo.getId();

                // 文章-标签关联关系
                ArticleTagRelDO articleTagRelDO = ArticleTagRelDO.builder()
                        .tagId(tagId)
                        .articleId(articleId)
                        .build();
                articleTagRelDOS.add(articleTagRelDO);
            });
            //批量插入
            articleTagRelMapper.insertBatchSomeColumn(articleTagRelDOS);
        }

    }


    /**
     * 删除文章
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response deleteArticle(DeleteArticleReqVO deleteArticleReqVO) {
        Long articleId = deleteArticleReqVO.getId();
        // 1. 删除文章
        articleMapper.deleteById(articleId);

        // 2. 删除文章内容
        articleContentMapper.deleteByArticleId(articleId);

        // 3. 删除文章-分类关联记录
        articleCategoryRelMapper.deleteByArticleId(articleId);

        // 4. 删除文章-标签关联记录
        articleTagRelMapper.deleteByArticleId(articleId);

        return Response.success();
    }

    /**
     * 查询文章分页数据
     */
    @Override
    public Response findArticlePageList(FindArticlePageListReqVO findArticlePageListReqVO) {
        // 获得当前数据
        String title = findArticlePageListReqVO.getTitle();
        Long current = findArticlePageListReqVO.getCurrent();
        Long size = findArticlePageListReqVO.getSize();
        LocalDate startDate = findArticlePageListReqVO.getStartDate();
        LocalDate endDate = findArticlePageListReqVO.getEndDate();

        // 执行分页查询
        Page<ArticleDO> articleDOPage = articleMapper.selectPageList(title, current, size, startDate, endDate);
        List<ArticleDO> articleDos = articleDOPage.getRecords();

        // DO 转 VO
        List<FindArticlePageListRspVO> vos = null;
        if (!CollectionUtils.isEmpty(articleDos)) {
            vos = articleDos.stream().map(articleDo -> FindArticlePageListRspVO.builder()
                    .id(articleDo.getId())
                    .title(articleDo.getTitle())
                    .cover(articleDo.getCover())
                    .createTime(articleDo.getCreateTime())
                    .build())
                    .toList();
        }
        return PageResponse.success(articleDOPage,vos);
    }

    /**
     * 查询文章详情
     */
    @Override
    public Response findArticleDetail(FindArticleDetailReqVO findArticleDetailReqVO) {
        Long articleId = findArticleDetailReqVO.getId();

        ArticleDO articleDO = articleMapper.selectById(articleId);

        if (Objects.isNull(articleDO)) {
            log.warn("==> 查询文章不存在，articleId: {}", articleId);
            throw new BizException(ResponseCodeEnum.ARTICLE_NOT_FOUND);
        }

        // 文章内容
        ArticleContentDO articleContentDO = articleContentMapper.selectByArticleId(articleId);

        // 文章分类
        ArticleCategoryRelDO articleCategoryRelDO = articleCategoryRelMapper.selectByArticleId(articleId);

        // 文章标签
        List<ArticleTagRelDO> articleTagRelDOS = articleTagRelMapper.selectByArticleId(articleId);
        List<Long> tagIds = articleTagRelDOS.stream().map(ArticleTagRelDO::getTagId).toList();

        // DO转VO
        FindArticleDetailRspVO vo = FindArticleDetailRspVO.builder()
                .title(articleDO.getTitle())
                .tagIds(tagIds)
                .cover(articleDO.getCover())
                .summary(articleDO.getSummary())
                .content(articleContentDO.getContent())
                .categoryId(articleCategoryRelDO.getCategoryId())
                .id(articleId)
                .build();

        return Response.success(vo);
    }

    /**
     * 更新文章
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response updateArticle(UpdateArticleReqVO updateArticleReqVO) {
        Long articleId = updateArticleReqVO.getId();

        // VO 转 ArticleDO
        ArticleDO articleDO = ArticleDO.builder()
                .id(articleId)
                .title(updateArticleReqVO.getTitle())
                .cover(updateArticleReqVO.getCover())
                .summary(updateArticleReqVO.getSummary())
                .updateTime(LocalDateTime.now())
                .build();
        int count = articleMapper.updateById(articleDO);

        // 根据更新是否成功，来判断该文章是否存在
        if (count == 0) {
            log.warn("==> 文章不存在，articleId: {}", articleId);
            throw new BizException(ResponseCodeEnum.ARTICLE_NOT_FOUND);
        }

        // VO 转 ArticleContentDO，并更新
        ArticleContentDO articleContentDO = ArticleContentDO.builder()
                .articleId(articleId)
                .content(updateArticleReqVO.getContent())
                .build();
        articleContentMapper.updateByArticleId(articleContentDO);

        // 更新文章分类
        Long categoryId = updateArticleReqVO.getCategoryId();

        // 校验分类是否存在
        CategoryDo categoryDo = categoryMapper.selectById(categoryId);
        if (Objects.isNull(categoryDo)) {
            log.warn("==> 分类不存在，categoryId: {}", categoryId);
            throw new BizException(ResponseCodeEnum.CATEGORY_NOT_EXISTED);
        }

        // 先删除该文章关联的分类记录，再插入新的关联关系
        articleCategoryRelMapper.deleteByArticleId(articleId);
        ArticleCategoryRelDO articleCategoryRelDO = ArticleCategoryRelDO.builder()
                .categoryId(categoryId)
                .articleId(articleId)
                .build();
        articleCategoryRelMapper.insert(articleCategoryRelDO);

        // 保存文章关联的标签集合,先删除该文章对应的标签
        articleTagRelMapper.deleteByArticleId(articleId);
        List<String> tags = updateArticleReqVO.getTags();
        insertTags(articleId, tags);

        return Response.success();
    }
}
